package com.wang.tool.webtool;

import com.alibaba.fastjson.JSONObject;
import com.wang.tool.common.ErrorInfo;
import com.wang.tool.common.SimpleException;
import com.wang.tool.common.result.SimpleResult;
import com.wang.tool.monitor.LinkLogUtil;
import com.wang.tool.monitor.translator.GlobalErrorTranslator;
import org.springframework.beans.PropertyAccessException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.Objects;

/**
 * web响应处理
 * 不太好的地方就是这个功能劫持了ControllerAdvice
 * 系统中支持多个ControllerAdvice,这个异常处理器优先级必须最高,因为ControllerAdvice不是链式调用,别的优先级高只会覆盖此核心标记逻辑
 *
 * @Author wangshaopeng
 * @Date 2020/12/7
 */
@RestControllerAdvice
@Order(Integer.MIN_VALUE)
public class WrapResponseAdvice implements ResponseBodyAdvice<Object> {
    private static final String REQUEST_IS_INTERNAL_ERROR = "requestIsInternalError";

    public static final String HTTP_BUILD_4_INTERNAL_ERROR_ERROR = "HttpBuild4InternalErrorError";
    public static final String HTTP_BUILD_4_ERROR_URI_ERROR = "HttpBuild4ErrorURIError";

    /**
     * 系统默认 error请求地址
     * 2.3后已经废弃掉errorController 的errPath()了,将采用这个配置
     * 该属性的意义在于判别响应数据的requestRRI是否是全局异常地址
     */
    @Value("${server.error.path:/error}")
    private String errorUri;

    /**
     * 响应构建器
     */
    @Autowired
    private WebErrorResponseBuilder webResponseBuilder;

    //全局异常捕获,记录异常
    @ExceptionHandler(Throwable.class)
    public Object handleException(HttpServletRequest request, HttpServletResponse response, Throwable e) {
        //这个异常捕获其实是捕获到controller内部的异常
        //假如这个请求没有进到controller就出错了,filterChain先走Filter,Interceptor 出错了,那么在这里是拦截不了的
        //异常标识,后面在beforeBodyWrite会消费处理
        request.setAttribute(REQUEST_IS_INTERNAL_ERROR, e);
        return e;
    }


    @Override
    public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> messageConverter) {
        //进入了拦截器打了标记,就算是/error请求,也会经过拦截器有这个标记
        try {
            return ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest() != null;
        } catch (Throwable e) {
            return false;
        }
    }

    @Override
    public Object beforeBodyWrite(Object returnVal, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> messageConverter, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        //应该都是true
        if (!(serverHttpRequest instanceof ServletServerHttpRequest && serverHttpResponse instanceof ServletServerHttpResponse)) {
            return returnVal;
        }
        HttpServletRequest request = ((ServletServerHttpRequest) serverHttpRequest).getServletRequest();
        HttpServletResponse response = ((ServletServerHttpResponse) serverHttpResponse).getServletResponse();

        //内部异常
        Throwable e = (Throwable) request.getAttribute(REQUEST_IS_INTERNAL_ERROR);
        if (e != null) {
            SimpleResult<Void> defErrorResult = defaultInternalErrorResult(e,request);
            try {
                return webResponseBuilder.build4InternalError(defErrorResult, e, request, response);
            } catch (Throwable ex) {
                ErrorInfo error = GlobalErrorTranslator.translate(ex);
                LinkLogUtil.error(HTTP_BUILD_4_INTERNAL_ERROR_ERROR, e, "[{0}][{1}][{2}]", request.getRequestURI(), error.getErrorCode(), error.getErrorMsg());
                return defErrorResult;
            }
        }
        //正常请求
        if (!Objects.equals(request.getRequestURI(), errorUri)) {
            return returnVal;
        }
        // error解析,这种请求应该是内部异常转发而到/error
        SimpleResult<Void> defErrorURIResult = defaultErrorURIResult(returnVal);
        try {
            return webResponseBuilder.build4ErrorURI(defErrorURIResult, response);
        } catch (Throwable e2) {
            ErrorInfo error = GlobalErrorTranslator.translate(e2);
            //这个异常要抓稳,既然是error的响应,无论如何都给他回个东西
            LinkLogUtil.error(HTTP_BUILD_4_ERROR_URI_ERROR, e2, "[{0}][{1}][{2}]", request.getRequestURI(), error.getErrorCode(), error.getErrorMsg());
            return defErrorURIResult;
        }
    }


    /**
     * 转发至/error的请求要响应数据,根据ErrorAttr获取
     *
     * @param errorAttr
     * @return
     */
    private SimpleResult<Void> defaultErrorURIResult(Object errorAttr) {
        //默认解析
        String code = SimpleException.UNKNOWN_ERROR;
        String message = SimpleException.UNKNOWN_ERROR;
        if (!(errorAttr instanceof Map)) {
            return SimpleResult.error(code, message);
        }
        //系统默认DefaultErrorAttributes带有这属性,尽量取出来如果自定义,没辙
        try {
            code = ((Map<?, ?>) errorAttr).get("status").toString();
        } catch (Throwable ignore) {
        }
        try {
            message = ((Map<?, ?>) errorAttr).get("message").toString();
        } catch (Throwable ignore) {
        }
        SimpleResult<Void> result = SimpleResult.error(code, message);
        try {
            JSONObject extInfo = new JSONObject();
            extInfo.putAll((Map<? extends String, ?>) errorAttr);
            result.setExt(extInfo);
        } catch (Throwable ignore) {
        }
        return result;
    }


    /**
     * 解析异常Response
     *
     * @param e
     * @param request
     * @return
     */
    private SimpleResult<Void> defaultInternalErrorResult(Throwable e, HttpServletRequest request) {
        SimpleResult<Void> result;
        if (e instanceof IllegalArgumentException/*controller内部手动抛出该异常*/
                || e instanceof PropertyAccessException/*传参与参数类型不匹配*/
                || e instanceof ServletRequestBindingException/*requestParam 必填*/
                || e instanceof BindException/*requestParam valid*/
                || e instanceof IllegalStateException/*基础类型参数未传参*/
                || e instanceof HttpMessageConversionException/*requestBody解析失败*/
                || e instanceof MethodArgumentNotValidException/*requestBody valid*/
        ) {
            //controller参数有问题
            result = SimpleResult.error(SimpleException.PARAM_INVALID, e.getMessage());
        } else if (e instanceof SimpleException) {
            //指定业务异常
            result = SimpleResult.error(((SimpleException) e).getCode(), e.getMessage());
        } else if (e instanceof UnsupportedOperationException) {
            //不支持
            result = SimpleResult.error(SimpleException.UN_SUPPORT, e.getMessage());
        } else if (e instanceof NullPointerException) {
            //空指针
            result = SimpleResult.error(SimpleException.NPE, SimpleException.NPE);
        } else if (e.getClass().getSimpleName().equals("RpcException")) {
            result = SimpleResult.error(SimpleException.RPC_ERROR, SimpleException.RPC_ERROR);
        } else {
            //默认
            result = SimpleResult.error(SimpleException.UNKNOWN_ERROR, e.getMessage());
        }
        JSONObject extInfo = new JSONObject();
        extInfo.put("path", request.getRequestURI());
        result.setExt(extInfo);
        return result;
    }

}
